package datastore

import (
	"context"

	"github.com/pkg/errors"
	"github.com/stackrox/rox/central/globaldb"
	"github.com/stackrox/rox/central/imageintegration/store"
	pgStore "github.com/stackrox/rox/central/imageintegration/store/postgres"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/pkg/env"
	"github.com/stackrox/rox/pkg/features"
	"github.com/stackrox/rox/pkg/openshift"
	"github.com/stackrox/rox/pkg/sac"
	scannerTypes "github.com/stackrox/rox/pkg/scanners/types"
	"github.com/stackrox/rox/pkg/sync"
	"github.com/stackrox/rox/pkg/utils"
)

var (
	once sync.Once

	dataStore DataStore
)

func initializeIntegrations(iiStore store.Store) {
	ctx := sac.WithGlobalAccessScopeChecker(context.Background(), sac.AllowAllAccessScopeChecker())
	var iis []*storage.ImageIntegration
	err := iiStore.Walk(ctx, func(ii *storage.ImageIntegration) error {
		iis = append(iis, ii)
		return nil
	})
	utils.CrashOnError(err)
	// If we are starting from scratch in online-mode, add the default image integrations.
	if !env.OfflineModeEnv.BooleanSetting() && len(iis) == 0 {
		// Add default integrations
		utils.Should(iiStore.UpsertMany(ctx, store.DefaultImageIntegrations))
	}

	deleteAutogeneratedRegistries(ctx, iiStore, iis)

	setupScannerV4Integration(ctx, iiStore, iis)
}

func deleteAutogeneratedRegistries(ctx context.Context, iiStore store.Store, iis []*storage.ImageIntegration) {
	autogenEnabled := !env.AutogeneratedRegistriesDisabled.BooleanSetting()
	sourcedAutogenEnabled := features.SourcedAutogeneratedIntegrations.Enabled()
	globalPullAutogenEnabled := env.AutogenerateGlobalPullSecRegistries.BooleanSetting()

	if autogenEnabled && sourcedAutogenEnabled && globalPullAutogenEnabled {
		// All autogenerate features are enabled, nothing to delete.
		return
	}

	if len(iis) == 0 {
		// No integrations to process, short-circuit.
		return
	}

	log.Infof("[STARTUP] Starting deletion of autogenerated image integrations (autogen enabled [%t], 'sourced' autogen enabled [%t], global pull autogen enabled [%t])", autogenEnabled, sourcedAutogenEnabled, globalPullAutogenEnabled)

	var idsToDelete []string
	for _, ii := range iis {
		if shouldDeleteIntegration(ii, autogenEnabled, sourcedAutogenEnabled, globalPullAutogenEnabled) {
			idsToDelete = append(idsToDelete, ii.GetId())
		}
	}

	if len(idsToDelete) == 0 {
		log.Info("No eligible autogenerated image integrations found for deletion")
		// Use Should so release versions do not panic.
	} else if err := utils.ShouldErr(iiStore.PruneMany(ctx, idsToDelete)); err == nil {
		log.Infof("Successfully deleted %d image integration(s)", len(idsToDelete))
	}

	log.Info("Completed deletion of image integrations")
}

// shouldDeleteIntegration will return true if this integration should be deleted from the store.
func shouldDeleteIntegration(ii *storage.ImageIntegration, autogenEnabled, sourcedAutogenEnabled, globalPullAutogenEnabled bool) bool {
	if !ii.GetAutogenerated() {
		// Do not delete manually created integrations.
		return false
	}

	if !autogenEnabled {
		// If autogenerated integrations are disabled, then delete all autogenerated integrations.
		return true
	}

	if ii.GetSource() == nil {
		// The conditions following this one assume the integration has a source. If this integration doesn't
		// have a source then it should not be deleted.
		return false
	}

	if !sourcedAutogenEnabled && !globalPullAutogenEnabled {
		// If no features that use integration source are enabled and the integration has a source, delete it.
		return true
	}

	if !sourcedAutogenEnabled && globalPullAutogenEnabled && !openshift.GlobalPullSecretIntegration(ii) {
		// If only generating sourced integrations from the OCP global pull secret and this integration
		// is not from the OCP global pull secret, delete it.
		return true
	}

	return false
}

// setupScannerV4Integration will ensure the default Scanner V4 integration exists if
// Scanner V4 is installed/enabled (forces opt-in). If Scanner V4 is not installed (or is disabled)
// will delete all Scanner V4 integrations.
func setupScannerV4Integration(ctx context.Context, iiStore store.Store, iis []*storage.ImageIntegration) {
	keepDefault := true // for readability.

	// If Scanner V4 is not installed delete the associated integration(s).
	if !features.ScannerV4.Enabled() {
		deleteScannerV4Integrations(ctx, iiStore, iis, !keepDefault)
		return
	}

	// Create the default Scanner V4 integration if it doesn't exist.
	createDefaultScannerV4Integration(ctx, iiStore)

	// Delete all but the default Scanner V4 integration (should be none).
	deleteScannerV4Integrations(ctx, iiStore, iis, keepDefault)
}

// createDefaultScannerV4Integration will create the default Scanner V4 integration if it does
// not currently exist.
func createDefaultScannerV4Integration(ctx context.Context, iiStore store.Store) {
	if _, exists, err := iiStore.Get(ctx, store.DefaultScannerV4Integration.GetId()); err != nil {
		utils.Should(errors.Wrap(err, "unable to detect if default Scanner V4 integration exists"))
		return
	} else if exists {
		// Nothing to do, integration exists.
		return
	}

	log.Infof("Upserting default Scanner V4 integration %q (%s)", store.DefaultScannerV4Integration.GetName(), store.DefaultScannerV4Integration.GetId())
	err := iiStore.Upsert(ctx, store.DefaultScannerV4Integration)
	utils.Should(errors.Wrap(err, "unable to upsert default ScannerV4 integration"))
}

// deleteScannerV4Integrations will delete all Scanner V4 integrations except for
// the default integration if keepDefault is true.
func deleteScannerV4Integrations(ctx context.Context, iiStore store.Store, iis []*storage.ImageIntegration, keepDefault bool) {
	idsToDelete := make([]string, 0, len(iis))
	for _, ii := range iis {
		if ii.GetType() != scannerTypes.ScannerV4 {
			// Ignore non Scanner V4 integrations.
			continue
		}

		if keepDefault && ii.GetId() == store.DefaultScannerV4Integration.GetId() {
			// Keep the default Scanner V4 integration.
			continue
		}

		idsToDelete = append(idsToDelete, ii.GetId())
	}

	if len(idsToDelete) == 0 {
		return
	}

	if err := iiStore.PruneMany(ctx, idsToDelete); err != nil {
		utils.Should(errors.Wrap(err, "failed to delete Scanner V4 integrations"))
	}

	log.Infof("Deleted %d Scanner V4 integrations", len(idsToDelete))
}

func initialize() {
	// Create underlying store and datastore.
	storage := pgStore.New(globaldb.GetPostgres())

	initializeIntegrations(storage)
	dataStore = New(storage)
}

// Singleton provides the interface for non-service external interaction.
func Singleton() DataStore {
	once.Do(initialize)
	return dataStore
}
